import BoundingSphere from '../Core/BoundingSphere.js';
import Cartesian2 from '../Core/Cartesian2.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Cartesian4 from '../Core/Cartesian4.js';
import Cartographic from '../Core/Cartographic.js';
import clone from '../Core/clone.js';
import Color from '../Core/Color.js';
import combine from '../Core/combine.js';
import ComponentDatatype from '../Core/ComponentDatatype.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import defineProperties from '../Core/defineProperties.js';
import destroyObject from '../Core/destroyObject.js';
import DeveloperError from '../Core/DeveloperError.js';
import EncodedCartesian3 from '../Core/EncodedCartesian3.js';
import FeatureDetection from '../Core/FeatureDetection.js';
import Geometry from '../Core/Geometry.js';
import GeometryAttribute from '../Core/GeometryAttribute.js';
import GeometryAttributes from '../Core/GeometryAttributes.js';
import GeometryOffsetAttribute from '../Core/GeometryOffsetAttribute.js';
import Intersect from '../Core/Intersect.js';
import isArray from '../Core/isArray.js';
import Matrix4 from '../Core/Matrix4.js';
import Plane from '../Core/Plane.js';
import RuntimeError from '../Core/RuntimeError.js';
import subdivideArray from '../Core/subdivideArray.js';
import TaskProcessor from '../Core/TaskProcessor.js';
import BufferUsage from '../Renderer/BufferUsage.js';
import ContextLimits from '../Renderer/ContextLimits.js';
import DrawCommand from '../Renderer/DrawCommand.js';
import Pass from '../Renderer/Pass.js';
import RenderState from '../Renderer/RenderState.js';
import ShaderProgram from '../Renderer/ShaderProgram.js';
import ShaderSource from '../Renderer/ShaderSource.js';
import VertexArray from '../Renderer/VertexArray.js';
import when from '../ThirdParty/when.js';
import BatchTable from './BatchTable.js';
import CullFace from './CullFace.js';
import DepthFunction from './DepthFunction.js';
import PrimitivePipeline from './PrimitivePipeline.js';
import PrimitiveState from './PrimitiveState.js';
import SceneMode from './SceneMode.js';
import ShadowMode from './ShadowMode.js';

    /**
     * A primitive represents geometry in the {@link Scene}.  The geometry can be from a single {@link GeometryInstance}
     * as shown in example 1 below, or from an array of instances, even if the geometry is from different
     * geometry types, e.g., an {@link RectangleGeometry} and an {@link EllipsoidGeometry} as shown in Code Example 2.
     * <p>
     * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including
     * {@link Material} and {@link RenderState}.  Roughly, the geometry instance defines the structure and placement,
     * and the appearance defines the visual characteristics.  Decoupling geometry and appearance allows us to mix
     * and match most of them and add a new geometry or appearance independently of each other.
     * </p>
     * <p>
     * Combining multiple instances into one primitive is called batching, and significantly improves performance for static data.
     * Instances can be individually picked; {@link Scene#pick} returns their {@link GeometryInstance#id}.  Using
     * per-instance appearances like {@link PerInstanceColorAppearance}, each instance can also have a unique color.
     * </p>
     * <p>
     * {@link Geometry} can either be created and batched on a web worker or the main thread. The first two examples
     * show geometry that will be created on a web worker by using the descriptions of the geometry. The third example
     * shows how to create the geometry on the main thread by explicitly calling the <code>createGeometry</code> method.
     * </p>
     *
     * @alias Primitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {GeometryInstance[]|GeometryInstance} [options.geometryInstances] The geometry instances - or a single geometry instance - to render.
     * @param {Appearance} [options.appearance] The appearance used to render the primitive.
     * @param {Appearance} [options.depthFailAppearance] The appearance used to shade this primitive when it fails the depth test.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
     * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
     * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
     * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
     * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
     * @param {Boolean} [options.cull=true] When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume.  Set this to <code>false</code> for a small performance gain if you are manually culling the primitive.
     * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from light sources.
     *
     * @example
     * // 1. Draw a translucent ellipse on the surface with a checkerboard pattern
     * var instance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.EllipseGeometry({
     *       center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0),
     *       semiMinorAxis : 500000.0,
     *       semiMajorAxis : 1000000.0,
     *       rotation : Cesium.Math.PI_OVER_FOUR,
     *       vertexFormat : Cesium.VertexFormat.POSITION_AND_ST
     *   }),
     *   id : 'object returned when this instance is picked and to get/set per-instance attributes'
     * });
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : instance,
     *   appearance : new Cesium.EllipsoidSurfaceAppearance({
     *     material : Cesium.Material.fromType('Checkerboard')
     *   })
     * }));
     *
     * @example
     * // 2. Draw different instances each with a unique color
     * var rectangleInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0),
     *     vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
     *   }),
     *   id : 'rectangle',
     *   attributes : {
     *     color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
     *   }
     * });
     * var ellipsoidInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.EllipsoidGeometry({
     *     radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
     *     vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
     *   id : 'ellipsoid',
     *   attributes : {
     *     color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *   }
     * });
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : [rectangleInstance, ellipsoidInstance],
     *   appearance : new Cesium.PerInstanceColorAppearance()
     * }));
     *
     * @example
     * // 3. Create the geometry on the main thread.
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *       geometry : Cesium.EllipsoidGeometry.createGeometry(new Cesium.EllipsoidGeometry({
     *         radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
     *         vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
     *       })),
     *       modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *         Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
     *       id : 'ellipsoid',
     *       attributes : {
     *         color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *       }
     *   }),
     *   appearance : new Cesium.PerInstanceColorAppearance()
     * }));
     *
     * @see GeometryInstance
     * @see Appearance
     * @see ClassificationPrimitive
     * @see GroundPrimitive
     */
    function Primitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The geometry instances rendered with this primitive.  This may
         * be <code>undefined</code> if <code>options.releaseGeometryInstances</code>
         * is <code>true</code> when the primitive is constructed.
         * <p>
         * Changing this property after the primitive is rendered has no effect.
         * </p>
         *
         * @readonly
         * @type GeometryInstance[]|GeometryInstance
         *
         * @default undefined
         */
        this.geometryInstances = options.geometryInstances;

        /**
         * The {@link Appearance} used to shade this primitive. Each geometry
         * instance is shaded with the same appearance.  Some appearances, like
         * {@link PerInstanceColorAppearance} allow giving each instance unique
         * properties.
         *
         * @type Appearance
         *
         * @default undefined
         */
        this.appearance = options.appearance;
        this._appearance = undefined;
        this._material = undefined;

        /**
         * The {@link Appearance} used to shade this primitive when it fails the depth test. Each geometry
         * instance is shaded with the same appearance.  Some appearances, like
         * {@link PerInstanceColorAppearance} allow giving each instance unique
         * properties.
         *
         * <p>
         * When using an appearance that requires a color attribute, like PerInstanceColorAppearance,
         * add a depthFailColor per-instance attribute instead.
         * </p>
         *
         * <p>
         * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported,
         * there may be artifacts.
         * </p>
         * @type Appearance
         *
         * @default undefined
         */
        this.depthFailAppearance = options.depthFailAppearance;
        this._depthFailAppearance = undefined;
        this._depthFailMaterial = undefined;

        /**
         * The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
         * When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * <p>
         * This property is only supported in 3D mode.
         * </p>
         *
         * @type Matrix4
         *
         * @default Matrix4.IDENTITY
         *
         * @example
         * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
         * p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = new Matrix4();

        /**
         * Determines if the primitive will be shown.  This affects all geometry
         * instances in the primitive.
         *
         * @type Boolean
         *
         * @default true
         */
        this.show = defaultValue(options.show, true);

        this._vertexCacheOptimize = defaultValue(options.vertexCacheOptimize, false);
        this._interleave = defaultValue(options.interleave, false);
        this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true);
        this._allowPicking = defaultValue(options.allowPicking, true);
        this._asynchronous = defaultValue(options.asynchronous, true);
        this._compressVertices = defaultValue(options.compressVertices, true);

        /**
         * When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands
         * based on their bounding volume.  Set this to <code>false</code> for a small performance gain
         * if you are manually culling the primitive.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.cull = defaultValue(options.cull, true);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * @private
         */
        this.rtcCenter = options.rtcCenter;

        //>>includeStart('debug', pragmas.debug);
        if (defined(this.rtcCenter) && (!defined(this.geometryInstances) || (isArray(this.geometryInstances) && this.geometryInstances.length !== 1))) {
            throw new DeveloperError('Relative-to-center rendering only supports one geometry instance.');
        }
        //>>includeEnd('debug');

        /**
         * Determines whether this primitive casts or receives shadows from light sources.
         *
         * @type {ShadowMode}
         *
         * @default ShadowMode.DISABLED
         */
        this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED);

        this._translucent = undefined;

        this._state = PrimitiveState.READY;
        this._geometries = [];
        this._error = undefined;
        this._numberOfInstances = 0;

        this._boundingSpheres = [];
        this._boundingSphereWC = [];
        this._boundingSphereCV = [];
        this._boundingSphere2D = [];
        this._boundingSphereMorph = [];
        this._perInstanceAttributeCache = [];
        this._instanceIds = [];
        this._lastPerInstanceAttributeIndex = 0;

        this._va = [];
        this._attributeLocations = undefined;
        this._primitiveType = undefined;

        this._frontFaceRS = undefined;
        this._backFaceRS = undefined;
        this._sp = undefined;

        this._depthFailAppearance = undefined;
        this._spDepthFail = undefined;
        this._frontFaceDepthFailRS = undefined;
        this._backFaceDepthFailRS = undefined;

        this._pickIds = [];

        this._colorCommands = [];
        this._pickCommands = [];

        this._readOnlyInstanceAttributes = options._readOnlyInstanceAttributes;

        this._createBoundingVolumeFunction = options._createBoundingVolumeFunction;
        this._createRenderStatesFunction = options._createRenderStatesFunction;
        this._createShaderProgramFunction = options._createShaderProgramFunction;
        this._createCommandsFunction = options._createCommandsFunction;
        this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction;

        this._createPickOffsets = options._createPickOffsets;
        this._pickOffsets = undefined;

        this._createGeometryResults = undefined;
        this._ready = false;
        this._readyPromise = when.defer();

        this._batchTable = undefined;
        this._batchTableAttributeIndices = undefined;
        this._offsetInstanceExtend = undefined;
        this._batchTableOffsetAttribute2DIndex = undefined;
        this._batchTableOffsetsUpdated = false;
        this._instanceBoundingSpheres = undefined;
        this._instanceBoundingSpheresCV = undefined;
        this._tempBoundingSpheres = undefined;
        this._recomputeBoundingSpheres = false;
        this._batchTableBoundingSpheresUpdated = false;
        this._batchTableBoundingSphereAttributeIndices = undefined;
    }

    defineProperties(Primitive.prototype, {
        /**
         * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        vertexCacheOptimize : {
            get : function() {
                return this._vertexCacheOptimize;
            }
        },

        /**
         * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        interleave : {
            get : function() {
                return this._interleave;
            }
        },

        /**
         * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        releaseGeometryInstances : {
            get : function() {
                return this._releaseGeometryInstances;
            }
        },

        /**
         * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.         *
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        allowPicking : {
            get : function() {
                return this._allowPicking;
            }
        },

        /**
         * Determines if the geometry instances will be created and batched on a web worker.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        asynchronous : {
            get : function() {
                return this._asynchronous;
            }
        },

        /**
         * When <code>true</code>, geometry vertices are compressed, which will save memory.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        compressVertices : {
            get : function() {
                return this._compressVertices;
            }
        },

        /**
         * Determines if the primitive is complete and ready to render.  If this property is
         * true, the primitive will be rendered the next time that {@link Primitive#update}
         * is called.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves when the primitive is ready to render.
         * @memberof Primitive.prototype
         * @type {Promise.<Primitive>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        }
    });

    function getCommonPerInstanceAttributeNames(instances) {
        var length = instances.length;

        var attributesInAllInstances = [];
        var attributes0 = instances[0].attributes;
        var name;

        for (name in attributes0) {
            if (attributes0.hasOwnProperty(name) && defined(attributes0[name])) {
                var attribute = attributes0[name];
                var inAllInstances = true;

                // Does this same attribute exist in all instances?
                for (var i = 1; i < length; ++i) {
                    var otherAttribute = instances[i].attributes[name];

                    if (!defined(otherAttribute) ||
                        (attribute.componentDatatype !== otherAttribute.componentDatatype) ||
                        (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) ||
                        (attribute.normalize !== otherAttribute.normalize)) {

                        inAllInstances = false;
                        break;
                    }
                }

                if (inAllInstances) {
                    attributesInAllInstances.push(name);
                }
            }
        }

        return attributesInAllInstances;
    }

    var scratchGetAttributeCartesian2 = new Cartesian2();
    var scratchGetAttributeCartesian3 = new Cartesian3();
    var scratchGetAttributeCartesian4 = new Cartesian4();

    function getAttributeValue(value) {
        var componentsPerAttribute = value.length;
        if (componentsPerAttribute === 1) {
            return value[0];
        } else if (componentsPerAttribute === 2) {
            return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2);
        } else if (componentsPerAttribute === 3) {
            return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3);
        } else if (componentsPerAttribute === 4) {
            return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4);
        }
    }

    function createBatchTable(primitive, context) {
        var geometryInstances = primitive.geometryInstances;
        var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances];
        var numberOfInstances = instances.length;
        if (numberOfInstances === 0) {
            return;
        }

        var names = getCommonPerInstanceAttributeNames(instances);
        var length = names.length;

        var attributes = [];
        var attributeIndices = {};
        var boundingSphereAttributeIndices = {};
        var offset2DIndex;

        var firstInstance = instances[0];
        var instanceAttributes = firstInstance.attributes;

        var i;
        var name;
        var attribute;

        for (i = 0; i < length; ++i) {
            name = names[i];
            attribute = instanceAttributes[name];

            attributeIndices[name] = i;
            attributes.push({
                functionName : 'czm_batchTable_' + name,
                componentDatatype : attribute.componentDatatype,
                componentsPerAttribute : attribute.componentsPerAttribute,
                normalize : attribute.normalize
            });
        }

        if (names.indexOf('distanceDisplayCondition') !== -1) {
            attributes.push({
                functionName : 'czm_batchTable_boundingSphereCenter3DHigh',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            }, {
                functionName : 'czm_batchTable_boundingSphereCenter3DLow',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            }, {
                functionName : 'czm_batchTable_boundingSphereCenter2DHigh',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            }, {
                functionName : 'czm_batchTable_boundingSphereCenter2DLow',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            }, {
                functionName : 'czm_batchTable_boundingSphereRadius',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 1
            });
            boundingSphereAttributeIndices.center3DHigh = attributes.length - 5;
            boundingSphereAttributeIndices.center3DLow = attributes.length - 4;
            boundingSphereAttributeIndices.center2DHigh = attributes.length - 3;
            boundingSphereAttributeIndices.center2DLow = attributes.length - 2;
            boundingSphereAttributeIndices.radius = attributes.length - 1;
        }

        if (names.indexOf('offset') !== -1) {
            attributes.push({
                functionName : 'czm_batchTable_offset2D',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            });
            offset2DIndex = attributes.length - 1;
        }

        attributes.push({
            functionName : 'czm_batchTable_pickColor',
            componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
            componentsPerAttribute : 4,
            normalize : true
        });

        var attributesLength = attributes.length;
        var batchTable = new BatchTable(context, attributes, numberOfInstances);

        for (i = 0; i < numberOfInstances; ++i) {
            var instance = instances[i];
            instanceAttributes = instance.attributes;

            for (var j = 0; j < length; ++j) {
                name = names[j];
                attribute = instanceAttributes[name];
                var value = getAttributeValue(attribute.value);
                var attributeIndex = attributeIndices[name];
                batchTable.setBatchedAttribute(i, attributeIndex, value);
            }

            var pickObject = {
                primitive : defaultValue(instance.pickPrimitive, primitive)
            };

            if (defined(instance.id)) {
                pickObject.id = instance.id;
            }

            var pickId = context.createPickId(pickObject);
            primitive._pickIds.push(pickId);

            var pickColor = pickId.color;
            var color = scratchGetAttributeCartesian4;
            color.x = Color.floatToByte(pickColor.red);
            color.y = Color.floatToByte(pickColor.green);
            color.z = Color.floatToByte(pickColor.blue);
            color.w = Color.floatToByte(pickColor.alpha);

            batchTable.setBatchedAttribute(i, attributesLength - 1, color);
        }

        primitive._batchTable = batchTable;
        primitive._batchTableAttributeIndices = attributeIndices;
        primitive._batchTableBoundingSphereAttributeIndices = boundingSphereAttributeIndices;
        primitive._batchTableOffsetAttribute2DIndex = offset2DIndex;
    }

    function cloneAttribute(attribute) {
        var clonedValues;
        if (isArray(attribute.values)) {
            clonedValues = attribute.values.slice(0);
        } else {
            clonedValues = new attribute.values.constructor(attribute.values);
        }
        return new GeometryAttribute({
            componentDatatype : attribute.componentDatatype,
            componentsPerAttribute : attribute.componentsPerAttribute,
            normalize : attribute.normalize,
            values : clonedValues
        });
    }

    function cloneGeometry(geometry) {
        var attributes = geometry.attributes;
        var newAttributes = new GeometryAttributes();
        for (var property in attributes) {
            if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
                newAttributes[property] = cloneAttribute(attributes[property]);
            }
        }

        var indices;
        if (defined(geometry.indices)) {
            var sourceValues = geometry.indices;
            if (isArray(sourceValues)) {
                indices = sourceValues.slice(0);
            } else {
                indices = new sourceValues.constructor(sourceValues);
            }
        }

        return new Geometry({
            attributes : newAttributes,
            indices : indices,
            primitiveType : geometry.primitiveType,
            boundingSphere : BoundingSphere.clone(geometry.boundingSphere)
        });
    }

    function cloneInstance(instance, geometry) {
        return {
            geometry : geometry,
            attributes: instance.attributes,
            modelMatrix : Matrix4.clone(instance.modelMatrix),
            pickPrimitive : instance.pickPrimitive,
            id : instance.id
        };
    }

    var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g;

    Primitive._modifyShaderPosition = function(primitive, vertexShaderSource, scene3DOnly) {
        var match;

        var forwardDecl = '';
        var attributes = '';
        var computeFunctions = '';

        while ((match = positionRegex.exec(vertexShaderSource)) !== null) {
            var name = match[1];

            var functionName = 'vec4 czm_compute' + name[0].toUpperCase() + name.substr(1) + '()';

            // Don't forward-declare czm_computePosition because computePosition.glsl already does.
            if (functionName !== 'vec4 czm_computePosition()') {
                forwardDecl += functionName + ';\n';
            }

            if (!defined(primitive.rtcCenter)) {
                // Use GPU RTE
                if (!scene3DOnly) {
                    attributes +=
                        'attribute vec3 ' + name + '2DHigh;\n' +
                        'attribute vec3 ' + name + '2DLow;\n';

                    computeFunctions +=
                        functionName + '\n' +
                        '{\n' +
                        '    vec4 p;\n' +
                        '    if (czm_morphTime == 1.0)\n' +
                        '    {\n' +
                        '        p = czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
                        '    }\n' +
                        '    else if (czm_morphTime == 0.0)\n' +
                        '    {\n' +
                        '        p = czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy);\n' +
                        '    }\n' +
                        '    else\n' +
                        '    {\n' +
                        '        p = czm_columbusViewMorph(\n' +
                        '                czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy),\n' +
                        '                czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow),\n' +
                        '                czm_morphTime);\n' +
                        '    }\n' +
                        '    return p;\n' +
                        '}\n\n';
                } else {
                    computeFunctions +=
                        functionName + '\n' +
                        '{\n' +
                        '    return czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
                        '}\n\n';
                }
            } else {
                // Use RTC
                vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DHigh;/g, '');
                vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DLow;/g, '');

                forwardDecl += 'uniform mat4 u_modifiedModelView;\n';
                attributes += 'attribute vec4 position;\n';

                computeFunctions +=
                    functionName + '\n' +
                    '{\n' +
                    '    return u_modifiedModelView * position;\n' +
                    '}\n\n';

                vertexShaderSource = vertexShaderSource.replace(/czm_modelViewRelativeToEye\s+\*\s+/g, '');
                vertexShaderSource = vertexShaderSource.replace(/czm_modelViewProjectionRelativeToEye/g, 'czm_projection');
            }
        }

        return [forwardDecl, attributes, vertexShaderSource, computeFunctions].join('\n');
    };

    Primitive._appendShowToShader = function(primitive, vertexShaderSource) {
        if (!defined(primitive._batchTableAttributeIndices.show)) {
            return vertexShaderSource;
        }

        var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main');
        var showMain =
            'void main() \n' +
            '{ \n' +
            '    czm_non_show_main(); \n' +
            '    gl_Position *= czm_batchTable_show(batchId); \n' +
            '}';

        return renamedVS + '\n' + showMain;
    };

    Primitive._updateColorAttribute = function(primitive, vertexShaderSource, isDepthFail) {
        // some appearances have a color attribute for per vertex color.
        // only remove if color is a per instance attribute.
        if (!defined(primitive._batchTableAttributeIndices.color) && !defined(primitive._batchTableAttributeIndices.depthFailColor)) {
            return vertexShaderSource;
        }

        if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) {
            return vertexShaderSource;
        }

        //>>includeStart('debug', pragmas.debug);
        if (isDepthFail && !defined(primitive._batchTableAttributeIndices.depthFailColor)) {
            throw new DeveloperError('A depthFailColor per-instance attribute is required when using a depth fail appearance that uses a color attribute.');
        }
        //>>includeEnd('debug');

        var modifiedVS = vertexShaderSource;
        modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, '');
        if (!isDepthFail) {
            modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2');
        } else {
            modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_depthFailColor(batchId)$2');
        }
        return modifiedVS;
    };

    function appendPickToVertexShader(source) {
        var renamedVS = ShaderSource.replaceMain(source, 'czm_non_pick_main');
        var pickMain = 'varying vec4 v_pickColor; \n' +
                       'void main() \n' +
                       '{ \n' +
                       '    czm_non_pick_main(); \n' +
                       '    v_pickColor = czm_batchTable_pickColor(batchId); \n' +
                       '}';

        return renamedVS + '\n' + pickMain;
    }

    function appendPickToFragmentShader(source) {
        return 'varying vec4 v_pickColor;\n' + source;
    }

    Primitive._updatePickColorAttribute = function(source) {
        var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, '');
        vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2');
        return vsPick;
    };

    Primitive._appendOffsetToShader = function(primitive, vertexShaderSource) {
        if (!defined(primitive._batchTableAttributeIndices.offset)) {
            return vertexShaderSource;
        }

        var attr = 'attribute float batchId;\n';
        attr += 'attribute float applyOffset;';
        var modifiedShader = vertexShaderSource.replace(/attribute\s+float\s+batchId;/g, attr);

        var str = 'vec4 $1 = czm_computePosition();\n';
        str += '    if (czm_sceneMode == czm_sceneMode3D)\n';
        str += '    {\n';
        str += '        $1 = $1 + vec4(czm_batchTable_offset(batchId) * applyOffset, 0.0);';
        str += '    }\n';
        str += '    else\n';
        str += '    {\n';
        str += '        $1 = $1 + vec4(czm_batchTable_offset2D(batchId) * applyOffset, 0.0);';
        str += '    }\n';
        modifiedShader = modifiedShader.replace(/vec4\s+([A-Za-z0-9_]+)\s+=\s+czm_computePosition\(\);/g, str);
        return modifiedShader;
    };

    Primitive._appendDistanceDisplayConditionToShader = function(primitive, vertexShaderSource, scene3DOnly) {
        if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition)) {
            return vertexShaderSource;
        }

        var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_distanceDisplayCondition_main');
        var distanceDisplayConditionMain =
            'void main() \n' +
            '{ \n' +
            '    czm_non_distanceDisplayCondition_main(); \n' +
            '    vec2 distanceDisplayCondition = czm_batchTable_distanceDisplayCondition(batchId);\n' +
            '    vec3 boundingSphereCenter3DHigh = czm_batchTable_boundingSphereCenter3DHigh(batchId);\n' +
            '    vec3 boundingSphereCenter3DLow = czm_batchTable_boundingSphereCenter3DLow(batchId);\n' +
            '    float boundingSphereRadius = czm_batchTable_boundingSphereRadius(batchId);\n';

        if (!scene3DOnly) {
            distanceDisplayConditionMain +=
                '    vec3 boundingSphereCenter2DHigh = czm_batchTable_boundingSphereCenter2DHigh(batchId);\n' +
                '    vec3 boundingSphereCenter2DLow = czm_batchTable_boundingSphereCenter2DLow(batchId);\n' +
                '    vec4 centerRTE;\n' +
                '    if (czm_morphTime == 1.0)\n' +
                '    {\n' +
                '        centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n' +
                '    }\n' +
                '    else if (czm_morphTime == 0.0)\n' +
                '    {\n' +
                '        centerRTE = czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy);\n' +
                '    }\n' +
                '    else\n' +
                '    {\n' +
                '        centerRTE = czm_columbusViewMorph(\n' +
                '                czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy),\n' +
                '                czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow),\n' +
                '                czm_morphTime);\n' +
                '    }\n';
        } else {
            distanceDisplayConditionMain +=
                '    vec4 centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n';
        }

        distanceDisplayConditionMain +=
            '    float radiusSq = boundingSphereRadius * boundingSphereRadius; \n' +
            '    float distanceSq; \n' +
            '    if (czm_sceneMode == czm_sceneMode2D) \n' +
            '    { \n' +
            '        distanceSq = czm_eyeHeight2D.y - radiusSq; \n' +
            '    } \n' +
            '    else \n' +
            '    { \n' +
            '        distanceSq = dot(centerRTE.xyz, centerRTE.xyz) - radiusSq; \n' +
            '    } \n' +
            '    distanceSq = max(distanceSq, 0.0); \n' +
            '    float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; \n' +
            '    float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; \n' +
            '    float show = (distanceSq >= nearSq && distanceSq <= farSq) ? 1.0 : 0.0; \n' +
            '    gl_Position *= show; \n' +
            '}';
        return renamedVS + '\n' + distanceDisplayConditionMain;
    };

    function modifyForEncodedNormals(primitive, vertexShaderSource) {
        if (!primitive.compressVertices) {
            return vertexShaderSource;
        }

        var containsNormal = vertexShaderSource.search(/attribute\s+vec3\s+normal;/g) !== -1;
        var containsSt = vertexShaderSource.search(/attribute\s+vec2\s+st;/g) !== -1;
        if (!containsNormal && !containsSt) {
            return vertexShaderSource;
        }

        var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1;
        var containsBitangent = vertexShaderSource.search(/attribute\s+vec3\s+bitangent;/g) !== -1;

        var numComponents = containsSt && containsNormal ? 2.0 : 1.0;
        numComponents += containsTangent || containsBitangent ? 1 : 0;

        var type = (numComponents > 1) ? 'vec' + numComponents : 'float';

        var attributeName = 'compressedAttributes';
        var attributeDecl = 'attribute ' + type + ' ' + attributeName + ';';

        var globalDecl = '';
        var decode = '';

        if (containsSt) {
            globalDecl += 'vec2 st;\n';
            var stComponent = numComponents > 1 ? attributeName + '.x' : attributeName;
            decode += '    st = czm_decompressTextureCoordinates(' + stComponent + ');\n';
        }

        if (containsNormal && containsTangent && containsBitangent) {
            globalDecl +=
                'vec3 normal;\n' +
                'vec3 tangent;\n' +
                'vec3 bitangent;\n';
            decode += '    czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, bitangent);\n';
        } else {
            if (containsNormal) {
                globalDecl += 'vec3 normal;\n';
                decode += '    normal = czm_octDecode(' + attributeName + (numComponents > 1 ? '.' + (containsSt ? 'y' : 'x') : '') + ');\n';
            }

            if (containsTangent) {
                globalDecl += 'vec3 tangent;\n';
                decode += '    tangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
            }

            if (containsBitangent) {
                globalDecl += 'vec3 bitangent;\n';
                decode += '    bitangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
            }
        }

        var modifiedVS = vertexShaderSource;
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec2\s+st;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+tangent;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+bitangent;/g, '');
        modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main');
        var compressedMain =
            'void main() \n' +
            '{ \n' +
            decode +
            '    czm_non_compressed_main(); \n' +
            '}';

        return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n');
    }

    function depthClampVS(vertexShaderSource) {
        var modifiedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_depth_clamp_main');
        // The varying should be surround by #ifdef GL_EXT_frag_depth as an optimization.
        // It is not to workaround an issue with Edge:
        //     https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12120362/
        modifiedVS +=
            'varying float v_WindowZ;\n' +
            'void main() {\n' +
            '    czm_non_depth_clamp_main();\n' +
            '    vec4 position = gl_Position;\n' +
            '    v_WindowZ = (0.5 * (position.z / position.w) + 0.5) * position.w;\n' +
            '    position.z = min(position.z, position.w);\n' +
            '    gl_Position = position;\n' +
            '}\n';
        return modifiedVS;
    }

    function depthClampFS(fragmentShaderSource) {
        var modifiedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_non_depth_clamp_main');
        modifiedFS +=
            'varying float v_WindowZ;\n' +
            'void main() {\n' +
            '    czm_non_depth_clamp_main();\n' +
            '#if defined(GL_EXT_frag_depth) && !defined(LOG_DEPTH)\n' +
            '    gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n' +
            '#endif\n' +
            '}\n';
        modifiedFS =
            '#ifdef GL_EXT_frag_depth\n' +
            '#extension GL_EXT_frag_depth : enable\n' +
            '#endif\n' +
            modifiedFS;
        return modifiedFS;
    }

    function validateShaderMatching(shaderProgram, attributeLocations) {
        // For a VAO and shader program to be compatible, the VAO must have
        // all active attribute in the shader program.  The VAO may have
        // extra attributes with the only concern being a potential
        // performance hit due to extra memory bandwidth and cache pollution.
        // The shader source could have extra attributes that are not used,
        // but there is no guarantee they will be optimized out.
        //
        // Here, we validate that the VAO has all attributes required
        // to match the shader program.
        var shaderAttributes = shaderProgram.vertexAttributes;

        //>>includeStart('debug', pragmas.debug);
        for (var name in shaderAttributes) {
            if (shaderAttributes.hasOwnProperty(name)) {
                if (!defined(attributeLocations[name])) {
                    throw new DeveloperError('Appearance/Geometry mismatch.  The appearance requires vertex shader attribute input \'' + name +
                                             '\', which was not computed as part of the Geometry.  Use the appearance\'s vertexFormat property when constructing the geometry.');
                }
            }
        }
        //>>includeEnd('debug');
    }

    function getUniformFunction(uniforms, name) {
        return function() {
            return uniforms[name];
        };
    }

    var numberOfCreationWorkers = Math.max(FeatureDetection.hardwareConcurrency - 1, 1);
    var createGeometryTaskProcessors;
    var combineGeometryTaskProcessor = new TaskProcessor('combineGeometry', Number.POSITIVE_INFINITY);

    function loadAsynchronous(primitive, frameState) {
        var instances;
        var geometry;
        var i;
        var j;

        var instanceIds = primitive._instanceIds;

        if (primitive._state === PrimitiveState.READY) {
            instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
            var length = primitive._numberOfInstances = instances.length;

            var promises = [];
            var subTasks = [];
            for (i = 0; i < length; ++i) {
                geometry = instances[i].geometry;
                instanceIds.push(instances[i].id);

                //>>includeStart('debug', pragmas.debug);
                if (!defined(geometry._workerName)) {
                    throw new DeveloperError('_workerName must be defined for asynchronous geometry.');
                }
                //>>includeEnd('debug');

                subTasks.push({
                    moduleName : geometry._workerName,
                    geometry : geometry
                });
            }

            if (!defined(createGeometryTaskProcessors)) {
                createGeometryTaskProcessors = new Array(numberOfCreationWorkers);
                for (i = 0; i < numberOfCreationWorkers; i++) {
                    createGeometryTaskProcessors[i] = new TaskProcessor('createGeometry', Number.POSITIVE_INFINITY);
                }
            }

            var subTask;
            subTasks = subdivideArray(subTasks, numberOfCreationWorkers);

            for (i = 0; i < subTasks.length; i++) {
                var packedLength = 0;
                var workerSubTasks = subTasks[i];
                var workerSubTasksLength = workerSubTasks.length;
                for (j = 0; j < workerSubTasksLength; ++j) {
                    subTask = workerSubTasks[j];
                    geometry = subTask.geometry;
                    if (defined(geometry.constructor.pack)) {
                        subTask.offset = packedLength;
                        packedLength += defaultValue(geometry.constructor.packedLength, geometry.packedLength);
                    }
                }

                var subTaskTransferableObjects;

                if (packedLength > 0) {
                    var array = new Float64Array(packedLength);
                    subTaskTransferableObjects = [array.buffer];

                    for (j = 0; j < workerSubTasksLength; ++j) {
                        subTask = workerSubTasks[j];
                        geometry = subTask.geometry;
                        if (defined(geometry.constructor.pack)) {
                            geometry.constructor.pack(geometry, array, subTask.offset);
                            subTask.geometry = array;
                        }
                    }
                }

                promises.push(createGeometryTaskProcessors[i].scheduleTask({
                    subTasks : subTasks[i]
                }, subTaskTransferableObjects));
            }

            primitive._state = PrimitiveState.CREATING;

            when.all(promises, function(results) {
                primitive._createGeometryResults = results;
                primitive._state = PrimitiveState.CREATED;
            }).otherwise(function(error) {
                setReady(primitive, frameState, PrimitiveState.FAILED, error);
            });
        } else if (primitive._state === PrimitiveState.CREATED) {
            var transferableObjects = [];
            instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];

            var scene3DOnly = frameState.scene3DOnly;
            var projection = frameState.mapProjection;

            var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({
                createGeometryResults : primitive._createGeometryResults,
                instances : instances,
                ellipsoid : projection.ellipsoid,
                projection : projection,
                elementIndexUintSupported : frameState.context.elementIndexUint,
                scene3DOnly : scene3DOnly,
                vertexCacheOptimize : primitive.vertexCacheOptimize,
                compressVertices : primitive.compressVertices,
                modelMatrix : primitive.modelMatrix,
                createPickOffsets : primitive._createPickOffsets
            }, transferableObjects), transferableObjects);

            primitive._createGeometryResults = undefined;
            primitive._state = PrimitiveState.COMBINING;

            when(promise, function(packedResult) {
                var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult);
                primitive._geometries = result.geometries;
                primitive._attributeLocations = result.attributeLocations;
                primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
                primitive._pickOffsets = result.pickOffsets;
                primitive._offsetInstanceExtend = result.offsetInstanceExtend;
                primitive._instanceBoundingSpheres = result.boundingSpheres;
                primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;

                if (defined(primitive._geometries) && primitive._geometries.length > 0) {
                    primitive._recomputeBoundingSpheres = true;
                    primitive._state = PrimitiveState.COMBINED;
                } else {
                    setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
                }
            }).otherwise(function(error) {
                setReady(primitive, frameState, PrimitiveState.FAILED, error);
            });
        }
    }

    function loadSynchronous(primitive, frameState) {
        var instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
        var length = primitive._numberOfInstances = instances.length;
        var clonedInstances = new Array(length);
        var instanceIds = primitive._instanceIds;

        var instance;
        var i;

        var geometryIndex = 0;
        for (i = 0; i < length; i++) {
            instance = instances[i];
            var geometry = instance.geometry;

            var createdGeometry;
            if (defined(geometry.attributes) && defined(geometry.primitiveType)) {
                createdGeometry = cloneGeometry(geometry);
            } else {
                createdGeometry = geometry.constructor.createGeometry(geometry);
            }

            clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry);
            instanceIds.push(instance.id);
        }

        clonedInstances.length = geometryIndex;

        var scene3DOnly = frameState.scene3DOnly;
        var projection = frameState.mapProjection;

        var result = PrimitivePipeline.combineGeometry({
            instances : clonedInstances,
            ellipsoid : projection.ellipsoid,
            projection : projection,
            elementIndexUintSupported : frameState.context.elementIndexUint,
            scene3DOnly : scene3DOnly,
            vertexCacheOptimize : primitive.vertexCacheOptimize,
            compressVertices : primitive.compressVertices,
            modelMatrix : primitive.modelMatrix,
            createPickOffsets : primitive._createPickOffsets
        });

        primitive._geometries = result.geometries;
        primitive._attributeLocations = result.attributeLocations;
        primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
        primitive._pickOffsets = result.pickOffsets;
        primitive._offsetInstanceExtend = result.offsetInstanceExtend;
        primitive._instanceBoundingSpheres = result.boundingSpheres;
        primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;

        if (defined(primitive._geometries) && primitive._geometries.length > 0) {
            primitive._recomputeBoundingSpheres = true;
            primitive._state = PrimitiveState.COMBINED;
        } else {
            setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
        }
    }

    function recomputeBoundingSpheres(primitive, frameState) {
        var offsetIndex = primitive._batchTableAttributeIndices.offset;
        if (!primitive._recomputeBoundingSpheres || !defined(offsetIndex)) {
            primitive._recomputeBoundingSpheres = false;
            return;
        }

        var i;
        var offsetInstanceExtend = primitive._offsetInstanceExtend;
        var boundingSpheres = primitive._instanceBoundingSpheres;
        var length = boundingSpheres.length;
        var newBoundingSpheres = primitive._tempBoundingSpheres;
        if (!defined(newBoundingSpheres)) {
            newBoundingSpheres = new Array(length);
            for (i = 0; i < length; i++) {
                newBoundingSpheres[i] = new BoundingSphere();
            }
            primitive._tempBoundingSpheres = newBoundingSpheres;
        }
        for (i = 0; i < length; ++i) {
            var newBS = newBoundingSpheres[i];
            var offset = primitive._batchTable.getBatchedAttribute(i, offsetIndex, new Cartesian3());
            newBS = boundingSpheres[i].clone(newBS);
            transformBoundingSphere(newBS, offset, offsetInstanceExtend[i]);
        }
        var combinedBS = [];
        var combinedWestBS = [];
        var combinedEastBS = [];

        for (i = 0; i < length; ++i) {
            var bs = newBoundingSpheres[i];

            var minX = bs.center.x - bs.radius;
            if (minX > 0 || BoundingSphere.intersectPlane(bs, Plane.ORIGIN_ZX_PLANE) !== Intersect.INTERSECTING) {
                combinedBS.push(bs);
            } else {
                combinedWestBS.push(bs);
                combinedEastBS.push(bs);
            }
        }

        var resultBS1 = combinedBS[0];
        var resultBS2 = combinedEastBS[0];
        var resultBS3 = combinedWestBS[0];

        for (i = 1; i < combinedBS.length; i++) {
            resultBS1 = BoundingSphere.union(resultBS1, combinedBS[i]);
        }
        for (i = 1; i < combinedEastBS.length; i++) {
            resultBS2 = BoundingSphere.union(resultBS2, combinedEastBS[i]);
        }
        for (i = 1; i < combinedWestBS.length; i++) {
            resultBS3 = BoundingSphere.union(resultBS3, combinedWestBS[i]);
        }
        var result = [];
        if (defined(resultBS1)) {
            result.push(resultBS1);
        }
        if (defined(resultBS2)) {
            result.push(resultBS2);
        }
        if (defined(resultBS3)) {
            result.push(resultBS3);
        }

        for (i = 0; i < result.length; i++) {
            var boundingSphere = result[i].clone(primitive._boundingSpheres[i]);
            primitive._boundingSpheres[i] = boundingSphere;
            primitive._boundingSphereCV[i] = BoundingSphere.projectTo2D(boundingSphere, frameState.mapProjection, primitive._boundingSphereCV[i]);
        }

        Primitive._updateBoundingVolumes(primitive, frameState, primitive.modelMatrix, true);
        primitive._recomputeBoundingSpheres = false;
    }

    var scratchBoundingSphereCenterEncoded = new EncodedCartesian3();
    var scratchBoundingSphereCartographic = new Cartographic();
    var scratchBoundingSphereCenter2D = new Cartesian3();
    var scratchBoundingSphere = new BoundingSphere();

    function updateBatchTableBoundingSpheres(primitive, frameState) {
        var hasDistanceDisplayCondition = defined(primitive._batchTableAttributeIndices.distanceDisplayCondition);
        if (!hasDistanceDisplayCondition || primitive._batchTableBoundingSpheresUpdated) {
            return;
        }

        var indices = primitive._batchTableBoundingSphereAttributeIndices;
        var center3DHighIndex = indices.center3DHigh;
        var center3DLowIndex = indices.center3DLow;
        var center2DHighIndex = indices.center2DHigh;
        var center2DLowIndex = indices.center2DLow;
        var radiusIndex = indices.radius;

        var projection = frameState.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var batchTable = primitive._batchTable;
        var boundingSpheres = primitive._instanceBoundingSpheres;
        var length = boundingSpheres.length;

        for (var i = 0; i < length; ++i) {
            var boundingSphere = boundingSpheres[i];
            if (!defined(boundingSphere)) {
                continue;
            }

            var modelMatrix = primitive.modelMatrix;
            if (defined(modelMatrix)) {
                boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, scratchBoundingSphere);
            }

            var center = boundingSphere.center;
            var radius = boundingSphere.radius;

            var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded);
            batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high);
            batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low);

            if (!frameState.scene3DOnly) {
                var cartographic = ellipsoid.cartesianToCartographic(center, scratchBoundingSphereCartographic);
                var center2D = projection.project(cartographic, scratchBoundingSphereCenter2D);
                encodedCenter = EncodedCartesian3.fromCartesian(center2D, scratchBoundingSphereCenterEncoded);
                batchTable.setBatchedAttribute(i, center2DHighIndex, encodedCenter.high);
                batchTable.setBatchedAttribute(i, center2DLowIndex, encodedCenter.low);
            }

            batchTable.setBatchedAttribute(i, radiusIndex, radius);
        }

        primitive._batchTableBoundingSpheresUpdated = true;
    }

    var offsetScratchCartesian = new Cartesian3();
    var offsetCenterScratch = new Cartesian3();
    function updateBatchTableOffsets(primitive, frameState) {
        var hasOffset = defined(primitive._batchTableAttributeIndices.offset);
        if (!hasOffset || primitive._batchTableOffsetsUpdated || frameState.scene3DOnly) {
            return;
        }

        var index2D = primitive._batchTableOffsetAttribute2DIndex;

        var projection = frameState.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var batchTable = primitive._batchTable;
        var boundingSpheres = primitive._instanceBoundingSpheres;
        var length = boundingSpheres.length;

        for (var i = 0; i < length; ++i) {
            var boundingSphere = boundingSpheres[i];
            if (!defined(boundingSphere)) {
                continue;
            }
            var offset = batchTable.getBatchedAttribute(i, primitive._batchTableAttributeIndices.offset);
            if (Cartesian3.equals(offset, Cartesian3.ZERO)) {
                batchTable.setBatchedAttribute(i, index2D, Cartesian3.ZERO);
                continue;
            }

            var modelMatrix = primitive.modelMatrix;
            if (defined(modelMatrix)) {
                boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, scratchBoundingSphere);
            }

            var center = boundingSphere.center;
            center = ellipsoid.scaleToGeodeticSurface(center, offsetCenterScratch);
            var cartographic = ellipsoid.cartesianToCartographic(center, scratchBoundingSphereCartographic);
            var center2D = projection.project(cartographic, scratchBoundingSphereCenter2D);

            var newPoint = Cartesian3.add(offset, center, offsetScratchCartesian);
            cartographic = ellipsoid.cartesianToCartographic(newPoint, cartographic);

            var newPointProjected = projection.project(cartographic, offsetScratchCartesian);

            var newVector = Cartesian3.subtract(newPointProjected, center2D, offsetScratchCartesian);

            var x = newVector.x;
            newVector.x = newVector.z;
            newVector.z = newVector.y;
            newVector.y = x;

            batchTable.setBatchedAttribute(i, index2D, newVector);
        }

        primitive._batchTableOffsetsUpdated = true;
    }

    function createVertexArray(primitive, frameState) {
        var attributeLocations = primitive._attributeLocations;
        var geometries = primitive._geometries;
        var scene3DOnly = frameState.scene3DOnly;
        var context = frameState.context;

        var va = [];
        var length = geometries.length;
        for (var i = 0; i < length; ++i) {
            var geometry = geometries[i];

            va.push(VertexArray.fromGeometry({
                context : context,
                geometry : geometry,
                attributeLocations : attributeLocations,
                bufferUsage : BufferUsage.STATIC_DRAW,
                interleave : primitive._interleave
            }));

            if (defined(primitive._createBoundingVolumeFunction)) {
                primitive._createBoundingVolumeFunction(frameState, geometry);
            } else {
                primitive._boundingSpheres.push(BoundingSphere.clone(geometry.boundingSphere));
                primitive._boundingSphereWC.push(new BoundingSphere());

                if (!scene3DOnly) {
                    var center = geometry.boundingSphereCV.center;
                    var x = center.x;
                    var y = center.y;
                    var z = center.z;
                    center.x = z;
                    center.y = x;
                    center.z = y;

                    primitive._boundingSphereCV.push(BoundingSphere.clone(geometry.boundingSphereCV));
                    primitive._boundingSphere2D.push(new BoundingSphere());
                    primitive._boundingSphereMorph.push(new BoundingSphere());
                }
            }
        }

        primitive._va = va;
        primitive._primitiveType = geometries[0].primitiveType;

        if (primitive.releaseGeometryInstances) {
            primitive.geometryInstances = undefined;
        }

        primitive._geometries = undefined;
        setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined);
    }

    function createRenderStates(primitive, context, appearance, twoPasses) {
        var renderState = appearance.getRenderState();
        var rs;

        if (twoPasses) {
            rs = clone(renderState, false);
            rs.cull = {
                enabled : true,
                face : CullFace.BACK
            };
            primitive._frontFaceRS = RenderState.fromCache(rs);

            rs.cull.face = CullFace.FRONT;
            primitive._backFaceRS = RenderState.fromCache(rs);
        } else {
            primitive._frontFaceRS = RenderState.fromCache(renderState);
            primitive._backFaceRS = primitive._frontFaceRS;
        }

        rs = clone(renderState, false);
        if (defined(primitive._depthFailAppearance)) {
            rs.depthTest.enabled = false;
        }

        if (defined(primitive._depthFailAppearance)) {
            renderState = primitive._depthFailAppearance.getRenderState();
            rs = clone(renderState, false);
            rs.depthTest.func = DepthFunction.GREATER;
            if (twoPasses) {
                rs.cull = {
                    enabled : true,
                    face : CullFace.BACK
                };
                primitive._frontFaceDepthFailRS = RenderState.fromCache(rs);

                rs.cull.face = CullFace.FRONT;
                primitive._backFaceDepthFailRS = RenderState.fromCache(rs);
            } else {
                primitive._frontFaceDepthFailRS = RenderState.fromCache(rs);
                primitive._backFaceDepthFailRS = primitive._frontFaceRS;
            }
        }
    }

    function createShaderProgram(primitive, frameState, appearance) {
        var context = frameState.context;

        var attributeLocations = primitive._attributeLocations;

        var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource);
        vs = Primitive._appendOffsetToShader(primitive, vs);
        vs = Primitive._appendShowToShader(primitive, vs);
        vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly);
        vs = appendPickToVertexShader(vs);
        vs = Primitive._updateColorAttribute(primitive, vs, false);
        vs = modifyForEncodedNormals(primitive, vs);
        vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
        var fs = appearance.getFragmentShaderSource();
        fs = appendPickToFragmentShader(fs);

        primitive._sp = ShaderProgram.replaceCache({
            context : context,
            shaderProgram : primitive._sp,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });
        validateShaderMatching(primitive._sp, attributeLocations);

        if (defined(primitive._depthFailAppearance)) {
            vs = primitive._batchTable.getVertexShaderCallback()(primitive._depthFailAppearance.vertexShaderSource);
            vs = Primitive._appendShowToShader(primitive, vs);
            vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly);
            vs = appendPickToVertexShader(vs);
            vs = Primitive._updateColorAttribute(primitive, vs, true);
            vs = modifyForEncodedNormals(primitive, vs);
            vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
            vs = depthClampVS(vs);

            fs = primitive._depthFailAppearance.getFragmentShaderSource();
            fs = appendPickToFragmentShader(fs);
            fs = depthClampFS(fs);

            primitive._spDepthFail = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : primitive._spDepthFail,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
            validateShaderMatching(primitive._spDepthFail, attributeLocations);
        }
    }

    var modifiedModelViewScratch = new Matrix4();
    var rtcScratch = new Cartesian3();

    function getUniforms(primitive, appearance, material, frameState) {
        // Create uniform map by combining uniforms from the appearance and material if either have uniforms.
        var materialUniformMap = defined(material) ? material._uniforms : undefined;
        var appearanceUniformMap = {};
        var appearanceUniforms = appearance.uniforms;
        if (defined(appearanceUniforms)) {
            // Convert to uniform map of functions for the renderer
            for (var name in appearanceUniforms) {
                if (appearanceUniforms.hasOwnProperty(name)) {
                    //>>includeStart('debug', pragmas.debug);
                    if (defined(materialUniformMap) && defined(materialUniformMap[name])) {
                        // Later, we could rename uniforms behind-the-scenes if needed.
                        throw new DeveloperError('Appearance and material have a uniform with the same name: ' + name);
                    }
                    //>>includeEnd('debug');

                    appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name);
                }
            }
        }
        var uniforms = combine(appearanceUniformMap, materialUniformMap);
        uniforms = primitive._batchTable.getUniformMapCallback()(uniforms);

        if (defined(primitive.rtcCenter)) {
            uniforms.u_modifiedModelView = function() {
                var viewMatrix = frameState.context.uniformState.view;
                Matrix4.multiply(viewMatrix, primitive._modelMatrix, modifiedModelViewScratch);
                Matrix4.multiplyByPoint(modifiedModelViewScratch, primitive.rtcCenter, rtcScratch);
                Matrix4.setTranslation(modifiedModelViewScratch, rtcScratch, modifiedModelViewScratch);
                return modifiedModelViewScratch;
            };
        }

        return uniforms;
    }

    function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) {
        var uniforms = getUniforms(primitive, appearance, material, frameState);

        var depthFailUniforms;
        if (defined(primitive._depthFailAppearance)) {
            depthFailUniforms = getUniforms(primitive, primitive._depthFailAppearance, primitive._depthFailAppearance.material, frameState);
        }

        var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;

        var multiplier = twoPasses ? 2 : 1;
        multiplier *= defined(primitive._depthFailAppearance) ? 2 : 1;
        colorCommands.length = primitive._va.length * multiplier;

        var length = colorCommands.length;
        var vaIndex = 0;
        for (var i = 0; i < length; ++i) {
            var colorCommand;

            if (twoPasses) {
                colorCommand = colorCommands[i];
                if (!defined(colorCommand)) {
                    colorCommand = colorCommands[i] = new DrawCommand({
                        owner : primitive,
                        primitiveType : primitive._primitiveType
                    });
                }
                colorCommand.vertexArray = primitive._va[vaIndex];
                colorCommand.renderState = primitive._backFaceRS;
                colorCommand.shaderProgram = primitive._sp;
                colorCommand.uniformMap = uniforms;
                colorCommand.pass = pass;

                ++i;
            }

            colorCommand = colorCommands[i];
            if (!defined(colorCommand)) {
                colorCommand = colorCommands[i] = new DrawCommand({
                    owner : primitive,
                    primitiveType : primitive._primitiveType
                });
            }
            colorCommand.vertexArray = primitive._va[vaIndex];
            colorCommand.renderState = primitive._frontFaceRS;
            colorCommand.shaderProgram = primitive._sp;
            colorCommand.uniformMap = uniforms;
            colorCommand.pass = pass;

            if (defined(primitive._depthFailAppearance)) {
                if (twoPasses) {
                    ++i;

                    colorCommand = colorCommands[i];
                    if (!defined(colorCommand)) {
                        colorCommand = colorCommands[i] = new DrawCommand({
                            owner : primitive,
                            primitiveType : primitive._primitiveType
                        });
                    }
                    colorCommand.vertexArray = primitive._va[vaIndex];
                    colorCommand.renderState = primitive._backFaceDepthFailRS;
                    colorCommand.shaderProgram = primitive._spDepthFail;
                    colorCommand.uniformMap = depthFailUniforms;
                    colorCommand.pass = pass;
                }

                ++i;

                colorCommand = colorCommands[i];
                if (!defined(colorCommand)) {
                    colorCommand = colorCommands[i] = new DrawCommand({
                        owner : primitive,
                        primitiveType : primitive._primitiveType
                    });
                }
                colorCommand.vertexArray = primitive._va[vaIndex];
                colorCommand.renderState = primitive._frontFaceDepthFailRS;
                colorCommand.shaderProgram = primitive._spDepthFail;
                colorCommand.uniformMap = depthFailUniforms;
                colorCommand.pass = pass;
            }

            ++vaIndex;
        }
    }

    Primitive._updateBoundingVolumes = function(primitive, frameState, modelMatrix, forceUpdate) {
        var i;
        var length;
        var boundingSphere;

        if (forceUpdate || !Matrix4.equals(modelMatrix, primitive._modelMatrix)) {
            Matrix4.clone(modelMatrix, primitive._modelMatrix);
            length = primitive._boundingSpheres.length;
            for (i = 0; i < length; ++i) {
                boundingSphere = primitive._boundingSpheres[i];
                if (defined(boundingSphere)) {
                    primitive._boundingSphereWC[i] = BoundingSphere.transform(boundingSphere, modelMatrix, primitive._boundingSphereWC[i]);
                    if (!frameState.scene3DOnly) {
                        primitive._boundingSphere2D[i] = BoundingSphere.clone(primitive._boundingSphereCV[i], primitive._boundingSphere2D[i]);
                        primitive._boundingSphere2D[i].center.x = 0.0;
                        primitive._boundingSphereMorph[i] = BoundingSphere.union(primitive._boundingSphereWC[i], primitive._boundingSphereCV[i]);
                    }
                }
            }
        }

        // Update bounding volumes for primitives that are sized in pixels.
        // The pixel size in meters varies based on the distance from the camera.
        var pixelSize = primitive.appearance.pixelSize;
        if (defined(pixelSize)) {
            length = primitive._boundingSpheres.length;
            for (i = 0; i < length; ++i) {
                boundingSphere = primitive._boundingSpheres[i];
                var boundingSphereWC = primitive._boundingSphereWC[i];
                var pixelSizeInMeters = frameState.camera.getPixelSize(boundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
                var sizeInMeters = pixelSizeInMeters * pixelSize;
                boundingSphereWC.radius = boundingSphere.radius + sizeInMeters;
            }
        }
    };

    function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
        //>>includeStart('debug', pragmas.debug);
        if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) {
            throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.');
        }
        //>>includeEnd('debug');

        Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix);

        var boundingSpheres;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingSpheres = primitive._boundingSphereWC;
        } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
            boundingSpheres = primitive._boundingSphereCV;
        } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) {
            boundingSpheres = primitive._boundingSphere2D;
        } else if (defined(primitive._boundingSphereMorph)) {
            boundingSpheres = primitive._boundingSphereMorph;
        }

        var commandList = frameState.commandList;
        var passes = frameState.passes;
        if (passes.render || passes.pick) {
            var allowPicking = primitive.allowPicking;
            var castShadows = ShadowMode.castShadows(primitive.shadows);
            var receiveShadows = ShadowMode.receiveShadows(primitive.shadows);
            var colorLength = colorCommands.length;

            var factor = twoPasses ? 2 : 1;
            factor *= defined(primitive._depthFailAppearance) ? 2 : 1;

            for (var j = 0; j < colorLength; ++j) {
                var sphereIndex = Math.floor(j / factor);
                var colorCommand = colorCommands[j];
                colorCommand.modelMatrix = modelMatrix;
                colorCommand.boundingVolume = boundingSpheres[sphereIndex];
                colorCommand.cull = cull;
                colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
                colorCommand.castShadows = castShadows;
                colorCommand.receiveShadows = receiveShadows;

                if (allowPicking) {
                    colorCommand.pickId = 'v_pickColor';
                } else {
                    colorCommand.pickId = undefined;
                }

                commandList.push(colorCommand);
            }
        }
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} All instance geometries must have the same primitiveType.
     * @exception {DeveloperError} Appearance and material have a uniform with the same name.
     * @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode.
     * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
     */
    Primitive.prototype.update = function(frameState) {
        if (((!defined(this.geometryInstances)) && (this._va.length === 0)) ||
            (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length === 0) ||
            (!defined(this.appearance)) ||
            (frameState.mode !== SceneMode.SCENE3D && frameState.scene3DOnly) ||
            (!frameState.passes.render && !frameState.passes.pick)) {
            return;
        }

        if (defined(this._error)) {
            throw this._error;
        }

        //>>includeStart('debug', pragmas.debug);
        if (defined(this.rtcCenter) && !frameState.scene3DOnly) {
            throw new DeveloperError('RTC rendering is only available for 3D only scenes.');
        }
        //>>includeEnd('debug');

        if (this._state === PrimitiveState.FAILED) {
            return;
        }

        var context = frameState.context;
        if (!defined(this._batchTable)) {
            createBatchTable(this, context);
        }
        if (this._batchTable.attributes.length > 0) {
            if (ContextLimits.maximumVertexTextureImageUnits === 0) {
                throw new RuntimeError('Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.');
            }
            this._batchTable.update(frameState);
        }

        if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) {
            if (this.asynchronous) {
                loadAsynchronous(this, frameState);
            } else {
                loadSynchronous(this, frameState);
            }
        }

        if (this._state === PrimitiveState.COMBINED) {
            updateBatchTableBoundingSpheres(this, frameState);
            updateBatchTableOffsets(this, frameState);
            createVertexArray(this, frameState);
        }

        if (!this.show || this._state !== PrimitiveState.COMPLETE) {
            return;
        }

        if (!this._batchTableOffsetsUpdated) {
            updateBatchTableOffsets(this, frameState);
        }
        if (this._recomputeBoundingSpheres) {
            recomputeBoundingSpheres(this, frameState);
        }

        // Create or recreate render state and shader program if appearance/material changed
        var appearance = this.appearance;
        var material = appearance.material;
        var createRS = false;
        var createSP = false;

        if (this._appearance !== appearance) {
            this._appearance = appearance;
            this._material = material;
            createRS = true;
            createSP = true;
        } else if (this._material !== material) {
            this._material = material;
            createSP = true;
        }

        var depthFailAppearance = this.depthFailAppearance;
        var depthFailMaterial = defined(depthFailAppearance) ? depthFailAppearance.material : undefined;

        if (this._depthFailAppearance !== depthFailAppearance) {
            this._depthFailAppearance = depthFailAppearance;
            this._depthFailMaterial = depthFailMaterial;
            createRS = true;
            createSP = true;
        } else if (this._depthFailMaterial !== depthFailMaterial) {
            this._depthFailMaterial = depthFailMaterial;
            createSP = true;
        }

        var translucent = this._appearance.isTranslucent();
        if (this._translucent !== translucent) {
            this._translucent = translucent;
            createRS = true;
        }

        if (defined(this._material)) {
            this._material.update(context);
        }

        var twoPasses = appearance.closed && translucent;

        if (createRS) {
            var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates);
            rsFunc(this, context, appearance, twoPasses);
        }

        if (createSP) {
            var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram);
            spFunc(this, frameState, appearance);
        }

        if (createRS || createSP) {
            var commandFunc = defaultValue(this._createCommandsFunction, createCommands);
            commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState);
        }

        var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands);
        updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses);
    };

    var offsetBoundingSphereScratch1 = new BoundingSphere();
    var offsetBoundingSphereScratch2 = new BoundingSphere();
    function transformBoundingSphere(boundingSphere, offset, offsetAttribute) {
        if (offsetAttribute === GeometryOffsetAttribute.TOP) {
            var origBS = BoundingSphere.clone(boundingSphere, offsetBoundingSphereScratch1);
            var offsetBS = BoundingSphere.clone(boundingSphere, offsetBoundingSphereScratch2);
            offsetBS.center = Cartesian3.add(offsetBS.center, offset, offsetBS.center);
            boundingSphere = BoundingSphere.union(origBS, offsetBS, boundingSphere);
        } else if (offsetAttribute === GeometryOffsetAttribute.ALL) {
            boundingSphere.center = Cartesian3.add(boundingSphere.center, offset, boundingSphere.center);
        }

        return boundingSphere;
    }

    function createGetFunction(batchTable, instanceIndex, attributeIndex) {
        return function() {
            var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex);
            var attribute = batchTable.attributes[attributeIndex];
            var componentsPerAttribute = attribute.componentsPerAttribute;
            var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute);
            if (defined(attributeValue.constructor.pack)) {
                attributeValue.constructor.pack(attributeValue, value, 0);
            } else {
                value[0] = attributeValue;
            }
            return value;
        };
    }

    function createSetFunction(batchTable, instanceIndex, attributeIndex, primitive, name) {
        return function(value) {
            //>>includeStart('debug', pragmas.debug);
            if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) {
                throw new DeveloperError('value must be and array with length between 1 and 4.');
            }
            //>>includeEnd('debug');
            var attributeValue = getAttributeValue(value);
            batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue);
            if (name === 'offset') {
                primitive._recomputeBoundingSpheres = true;
                primitive._batchTableOffsetsUpdated = false;
            }
        };
    }

    var offsetScratch = new Cartesian3();

    function createBoundingSphereProperties(primitive, properties, index) {
        properties.boundingSphere = {
            get : function() {
                var boundingSphere = primitive._instanceBoundingSpheres[index];
                if (defined(boundingSphere)) {
                    boundingSphere = boundingSphere.clone();
                    var modelMatrix = primitive.modelMatrix;
                    var offset = properties.offset;
                    if (defined(offset)) {
                        transformBoundingSphere(boundingSphere, Cartesian3.fromArray(offset.get(), 0, offsetScratch), primitive._offsetInstanceExtend[index]);
                    }
                    if (defined(modelMatrix)) {
                        boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix);
                    }
                }

                return boundingSphere;
            }
        };
        properties.boundingSphereCV = {
            get : function() {
                return primitive._instanceBoundingSpheresCV[index];
            }
        };
    }

    function createPickIdProperty(primitive, properties, index) {
        properties.pickId = {
            get : function() {
                return primitive._pickIds[index];
            }
        };
    }

    /**
     * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
     *
     * @param {*} id The id of the {@link GeometryInstance}.
     * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
     *
     * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
     * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
     * attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(100.0, 10000.0);
     * attributes.offset = Cesium.OffsetGeometryInstanceAttribute.toValue(Cartesian3.IDENTITY);
     */
    Primitive.prototype.getGeometryInstanceAttributes = function(id) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(id)) {
            throw new DeveloperError('id is required');
        }
        if (!defined(this._batchTable)) {
            throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
        }
        //>>includeEnd('debug');

        var index = -1;
        var lastIndex = this._lastPerInstanceAttributeIndex;
        var ids = this._instanceIds;
        var length = ids.length;
        for (var i = 0; i < length; ++i) {
            var curIndex = (lastIndex + i) % length;
            if (id === ids[curIndex]) {
                index = curIndex;
                break;
            }
        }

        if (index === -1) {
            return undefined;
        }

        var attributes = this._perInstanceAttributeCache[index];
        if (defined(attributes)) {
            return attributes;
        }

        var batchTable = this._batchTable;
        var perInstanceAttributeIndices = this._batchTableAttributeIndices;
        attributes = {};
        var properties = {};

        for (var name in perInstanceAttributeIndices) {
            if (perInstanceAttributeIndices.hasOwnProperty(name)) {
                var attributeIndex = perInstanceAttributeIndices[name];
                properties[name] = {
                    get : createGetFunction(batchTable, index, attributeIndex)
                };

                var createSetter = true;
                var readOnlyAttributes = this._readOnlyInstanceAttributes;
                if (createSetter && defined(readOnlyAttributes)) {
                    length = readOnlyAttributes.length;
                    for (var k = 0; k < length; ++k) {
                        if (name === readOnlyAttributes[k]) {
                            createSetter = false;
                            break;
                        }
                    }
                }

                if (createSetter) {
                    properties[name].set = createSetFunction(batchTable, index, attributeIndex, this, name);
                }
            }
        }

        createBoundingSphereProperties(this, properties, index);
        createPickIdProperty(this, properties, index);
        defineProperties(attributes, properties);

        this._lastPerInstanceAttributeIndex = index;
        this._perInstanceAttributeCache[index] = attributes;
        return attributes;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Primitive#destroy
     */
    Primitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * e = e && e.destroy();
     *
     * @see Primitive#isDestroyed
     */
    Primitive.prototype.destroy = function() {
        var length;
        var i;

        this._sp = this._sp && this._sp.destroy();
        this._spDepthFail = this._spDepthFail && this._spDepthFail.destroy();

        var va = this._va;
        length = va.length;
        for (i = 0; i < length; ++i) {
            va[i].destroy();
        }
        this._va = undefined;

        var pickIds = this._pickIds;
        length = pickIds.length;
        for (i = 0; i < length; ++i) {
            pickIds[i].destroy();
        }
        this._pickIds = undefined;

        this._batchTable = this._batchTable && this._batchTable.destroy();

        //These objects may be fairly large and reference other large objects (like Entities)
        //We explicitly set them to undefined here so that the memory can be freed
        //even if a reference to the destroyed Primitive has been kept around.
        this._instanceIds = undefined;
        this._perInstanceAttributeCache = undefined;
        this._attributeLocations = undefined;

        return destroyObject(this);
    };

    function setReady(primitive, frameState, state, error) {
        primitive._error = error;
        primitive._state = state;
        frameState.afterRender.push(function() {
            primitive._ready = primitive._state === PrimitiveState.COMPLETE || primitive._state === PrimitiveState.FAILED;
            if (!defined(error)) {
                primitive._readyPromise.resolve(primitive);
            } else {
                primitive._readyPromise.reject(error);
            }
        });
    }
export default Primitive;
